﻿using System;
using System.Web;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using Noftware.SPArchiveTweets.TweetTimer;
using SharePointStu.SharePoint.Logging;
using System.Web.UI.HtmlControls;

namespace Noftware.SPArchiveTweets
{
    /// <summary>
    /// Codebehind for job management aspx file.
    /// </summary>
    public class ManageJob : LayoutsPageBase
    {
        // Dropdown list of schedule types
        protected DropDownList ddlSchedule;

        // Delete button
        protected LinkButton btnDelete;

        // Placeholder to show for existing job only - show delete section
        protected PlaceHolder phdExistingJobDelete;

        // JS opendialog to manage source twitter account list
        protected HyperLink lnkSourceModify;

        // Button to allow the user to create the lists
        protected LinkButton btnCreateLists;

        // Test valid SharePoint URL
        protected LinkButton btnTestUrl;

        // Test OAuth credentials
        protected LinkButton btnTestOAuth;

        // OK button
        protected Button btnOK;

        // Error messages label
        protected Label lblMessages;

        // SharePoint Site URL
        protected InputFormTextBox txtSharePointSiteUrl;

        // OAuth: Consumer Key textbox
        protected InputFormTextBox txtOAuthConsumerKey;

        // OAuth: Consumer Secret textbox
        protected InputFormTextBox txtOAuthConsumerSecret;

        // OAuth: Access Token
        protected InputFormTextBox txtOAuthAccessToken;

        // OAuth: Access Token Secret
        protected InputFormTextBox txtOAuthAccessTokenSecret;

        // Timeline: Include re-tweets
        protected DropDownList ddlIncludeRetweets;

        // Timeline: Include re-tweets
        protected DropDownList ddlIncludeEntities;

        // Timeline: Maximum number to retrieve
        protected TextBox txtTweetCount;

        // Placeholder to show/hide the TimePicker controls
        protected PlaceHolder phdScheduleTime;

        // Label to show/hide the 'Immediate' message
        protected Label lblManualMessage;

        // TimePicker repeater
        protected Repeater repPickerControls;

        // Configuration: Maximum amount of schedules to run
        protected Literal HoursBetweenIntervals;

        // Maximum number to run per day. Gets it value from HoursBetweenIntervals literal control.
        private int MaximumSchedules = 12;

        protected override bool RequireSiteAdministrator
        {
            get { return true; }
        }

        // Keeps track of the rows within the repeater
        private int _scheduleCounter = 0;

        /// <summary>
        /// Page initialization event handler.
        /// </summary>
        protected void Page_Init(object sender, EventArgs e)
        {
            // Allows the user to choose the schedule frequency
            ddlSchedule.SelectedIndexChanged += new EventHandler(ddlSchedule_SelectedIndexChanged);

            // Event handler called for each item bound to its data source
            this.repPickerControls.ItemDataBound += new RepeaterItemEventHandler(repPickerControls_ItemDataBound);
        }

        /// <summary>
        /// Page load event handler.
        /// </summary>
        protected void Page_Load(object sender, EventArgs e)
        {
            SPContext.Current.Web.AllowUnsafeUpdates = true;

            // Get the number of occurrences that this could run
            try
            {
                MaximumSchedules = int.Parse(this.HoursBetweenIntervals.Text);
            }
            catch (Exception)
            {
                MaximumSchedules = 12;
            }
            if (MaximumSchedules > 12)
            {
                MaximumSchedules = 12;
            }
            else if (MaximumSchedules < 0)
            {
                MaximumSchedules = 2;
            }

            try
            {
                if (this.IsPostBack == false)
                {
                    // Show the stored Schedule frequency in the dropdown
                    string selected = AppData.Local.Entries[AppData.SELECTED_VALUE];
                    if (ddlSchedule.Items.FindByValue(selected) != null)
                    {
                        ddlSchedule.SelectedValue = selected;
                    }
                    ddlSchedule_SelectedIndexChanged(this, System.EventArgs.Empty);

                    // Display previously persisted settings
                    txtSharePointSiteUrl.Text = AppData.Local.Entries[AppData.SHAREPOINT_SITE_URL] ?? string.Empty;
                    txtOAuthAccessToken.Text = AppData.Local.Entries[AppData.OAUTH_ACCESS_TOKEN] ?? string.Empty;

                    // Was a full URL defined?
                    bool listsAreCreated = false;
                    string url = txtSharePointSiteUrl.Text.Trim();
                    if (url != string.Empty && url != "http://")
                    {
                        // Ensure URL ends with a trailing slash
                        if (url.EndsWith("/") == false)
                        {
                            url += "/";
                            AppData.Local.Entries[AppData.SHAREPOINT_SITE_URL] = url;
                            txtSharePointSiteUrl.Text = url;
                        }

                        // If lists exist, show Delete section and Manage Accounts link.
                        // If not, then show Create Lists button.
                        listsAreCreated = areListsCreated();

                    } // if (url != string.Empty &&...

                    // If lists exist, show Delete section and Manage Accounts link.
                    // If not, then show Create Lists button.
                    this.setButtonControls(listsAreCreated);

                    // Set control values
                    ddlIncludeEntities.SelectedValue = AppData.Local.Entries[AppData.INCLUDE_ENTITIES] ?? "true";
                    ddlIncludeRetweets.SelectedValue = AppData.Local.Entries[AppData.INCLUDE_RETWEETS] ?? "true";
                    txtOAuthAccessTokenSecret.Text = AppData.Local.Entries[AppData.OAUTH_ACCESS_TOKEN_SECRET] ?? string.Empty;
                    txtOAuthConsumerKey.Text = AppData.Local.Entries[AppData.OAUTH_CONSUMER_KEY] ?? string.Empty;
                    txtOAuthConsumerSecret.Text = AppData.Local.Entries[AppData.OAUTH_CONSUMER_SECRET] ?? string.Empty;
                    txtTweetCount.Text = AppData.Local.Entries[AppData.TWEET_COUNT] ?? "50";
                }
            }
            catch (Exception ex)
            {
                lblMessages.Text = ex.Message;
                Logger.LogErrorLocal(EventId.Error, CategoryId.Error, ErrorLevel.Unexpected, "An error occurred in Twitter archive page load.", ex);
            }
        }

        /// <summary>
        /// Set the state of the action buttons based on if the lists are created or not.
        /// </summary>
        /// <param name="listsAreCreated">True if lists are created, false if otherwise.</param>
        private void setButtonControls(bool listsAreCreated)
        {
            // If lists exist, show Delete section and Manage Accounts link.
            // If not, then show Create Lists button.
            if (listsAreCreated == true)
            {
                // Lists are available
                // i.e. http://<your-url>/Lists/Twitter%20Accounts/AllItems.aspx?isDlg=1
                string url = AppData.Local.Entries[AppData.SHAREPOINT_SITE_URL];
                if (url.EndsWith("/") == false)
                {
                    url += "/";
                }
                string listUrl = url + "Lists/Twitter%20Accounts/AllItems.aspx";
                lnkSourceModify.Attributes["href"] = "javascript:void(0);";
                lnkSourceModify.Attributes["onclick"] = "javascript:displayModalUrl('Manage Twitter Accounts', '" + listUrl + "');";
                lnkSourceModify.Visible = true;
                phdExistingJobDelete.Visible = true;
                btnOK.Enabled = true;
                btnCreateLists.Visible = false;
            }
            else
            {
                // Lists not created
                btnOK.Enabled = false;
                phdExistingJobDelete.Visible = false;
                lnkSourceModify.Visible = false;
                btnCreateLists.Visible = true;
            }
        }

        /// <summary>
        /// Event handler of the Create Lists button to delete the lists.
        /// </summary>
        protected void btnCreateLists_Click(object sender, EventArgs e)
        {
            // Display modal message of results
            try
            {
                // Delete the lists
                string url = AppData.Local.Entries[AppData.SHAREPOINT_SITE_URL];
                using (var site = new SPSite(url))
                {
                    using (var web = site.RootWeb)
                    {
                        bool originalValue = web.AllowUnsafeUpdates;
                        web.AllowUnsafeUpdates = true;

                        // Delete the SP Archive Tweet Lists
                        var list = new SPTweetList();
                        list.CreateListsIfNotPresent(web);

                        web.AllowUnsafeUpdates = originalValue;
                    };
                };

                this.RenderSPDialogMessageBox("Success", "All lists have been created.");
                this.setButtonControls(true);
            }
            catch (Exception ex)
            {
                Logger.LogErrorLocal(EventId.Error, CategoryId.Error, ErrorLevel.High, "Tweet list creation not successful.", ex);
                this.RenderSPDialogMessageBox("Error", "Creation unsuccessful. Error: " + ex.Message);
            }
        }

        /// <summary>
        /// Event handler of the Delete button to delete the lists.
        /// </summary>
        protected void btnDelete_Click(object sender, EventArgs e)
        {
            // Display modal message of results
            try
            {
                // Delete the lists
                string url = AppData.Local.Entries[AppData.SHAREPOINT_SITE_URL];
                using (var site = new SPSite(url))
                {
                    using (var web = site.RootWeb)
                    {
                        bool originalValue = web.AllowUnsafeUpdates;
                        web.AllowUnsafeUpdates = true;

                        // Delete the SP Archive Tweet Lists
                        var list = new SPTweetList();
                        list.DeleteAllLists(web);

                        web.AllowUnsafeUpdates = originalValue;
                    };
                };

                this.setButtonControls(false);
                this.RenderSPDialogMessageBox("Success", "All lists have been deleted.");
            }
            catch (Exception ex)
            {
                Logger.LogErrorLocal(EventId.Error, CategoryId.Error, ErrorLevel.High, "Tweet list deletion not successful.", ex);
                this.RenderSPDialogMessageBox("Error", "Delete unsuccessful. Error: " + ex.Message);
            }
        }

        /// <summary>
        /// Ensure a key is present in the dictionary. If not, add it with the specified default value.
        /// </summary>
        /// <param name="key">Key.</param>
        /// <param name="defaultValue">Default value, if not present.</param>
        private void ensureKeyIsPresent(string key, string defaultValue)
        {
            // Ensure the key is present
            if (AppData.Local.Entries.ContainsKey(key) == false)
            {
                AppData.Local.Entries.Add(key, defaultValue);
            }
        }

        /// <summary>
        /// For each count in the SCHEDULE_COUNT setting, show a row with the TimePicker and delete button.
        /// </summary>
        private void loadFrequencySchedule()
        {
            // Bind a numeric list to the Repeater for each scheduled frequency found
            var items = new System.Collections.Generic.List<int>();
            int perHour = 24 / MaximumSchedules;
            for (int i = 1; i <= perHour; i++)
            {
                items.Add(i);
            }

            this.repPickerControls.DataSource = items;
            this.repPickerControls.DataBind();
        }

        /// <summary>
        /// Item DataBound event handler for each TimePicker/delete button in the repeater.
        /// </summary>
        private void repPickerControls_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
            {
                _scheduleCounter++;

                // Show the index of each item
                var ltlIndex = e.Item.FindControl("ltlIndex") as Literal;
                if (ltlIndex != null)
                {
                    ltlIndex.Text = _scheduleCounter.ToString();
                }

                // Are there any previously defined times to show in the timer controls?
                var ddlHour = e.Item.FindControl("ddlHour") as DropDownList;
                if (ddlHour != null)
                {
                    // Setup the Hours control
                    ddlHour.Items.Clear();
                    int perHour = 24 / MaximumSchedules;
                    int hourValue;
                    for (int i = 0; i < perHour; i++)
                    {
                        hourValue = i * MaximumSchedules;
                        switch (hourValue)
                        {
                            case 0:
                                {
                                    ddlHour.Items.Add(new ListItem("Midnight", hourValue.ToString()));
                                    break;
                                }
                            case 12:
                                {
                                    ddlHour.Items.Add(new ListItem("Noon", hourValue.ToString()));
                                    break;
                                }
                            default:
                                {
                                    string am_pm = hourValue < 12 ? "am" : "pm";
                                    int displayHours = hourValue < 12 ? hourValue : hourValue - 12;
                                    ddlHour.Items.Add(new ListItem(displayHours.ToString() + " " + am_pm, hourValue.ToString()));
                                    break;
                                }
                        } // switch (hourValue)
                    }

                    // Ensure there are default values for Enabled, Hours and Time
                    if (_scheduleCounter == 1)
                    {
                        // Always enable the first schedule
                        this.ensureKeyIsPresent(AppData.SCHEDULE_ENABLED + _scheduleCounter.ToString(), "True");
                    }
                    else
                    {
                        // Disable all other schedules unless the user explicitly enables it
                        this.ensureKeyIsPresent(AppData.SCHEDULE_ENABLED + _scheduleCounter.ToString(), "False");
                    }
                    this.ensureKeyIsPresent(AppData.SCHEDULE_HOUR_TIME + _scheduleCounter.ToString(), "0");

                    // Hour
                    int hour;
                    try
                    {
                        string temp = AppData.Local.Entries[AppData.SCHEDULE_HOUR_TIME + _scheduleCounter.ToString()];
                        hour = int.Parse(temp);
                        if (hour < 0 || hour > 23)
                        {
                            hour = 0;
                        }
                    }
                    catch (Exception)
                    {
                        hour = 0;
                    }

                    // Set hour
                    if (ddlHour.Items.FindByValue(hour.ToString()) != null)
                    {
                        ddlHour.SelectedValue = hour.ToString();
                    }
                }

                // Are there any previously defined times to show in the timer controls?
                var chkScheduleEnabled = e.Item.FindControl("chkScheduleEnabled") as CheckBox;
                if (chkScheduleEnabled != null)
                {
                    // Enabled
                    bool enabled;
                    try
                    {
                        string temp = AppData.Local.Entries[AppData.SCHEDULE_ENABLED + _scheduleCounter.ToString()];
                        enabled = bool.Parse(temp);
                    }
                    catch (Exception)
                    {
                        enabled = false;
                    }
                    chkScheduleEnabled.Checked = enabled;

                } // if (chkScheduleEnabled != null)

            } //  if (e.Item.ItemType == ListItemType.Item || e.Item...
        }

        /// <summary>
        /// Event handler of the user choosing a new schedule frequency.
        /// </summary>
        private void ddlSchedule_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Schedule new job
            bool validChoice = true;
            switch (ddlSchedule.SelectedValue)
            {
                case "Immediate":
                    {
                        lblManualMessage.Visible = true;
                        phdScheduleTime.Visible = false;

                        break;
                    }
                case "Daily":
                    {
                        lblManualMessage.Visible = false;
                        phdScheduleTime.Visible = true;

                        break;
                    }
                default:
                    {
                        lblManualMessage.Visible = false;
                        phdScheduleTime.Visible = false;
                        validChoice = false;

                        break;
                    }
            } // switch (ddlSchedule.Select...

            // Load the schedule
            if (validChoice == true)
            {
                this.loadFrequencySchedule();
            }
        }

        /// <summary>
        /// Event handler of the Test OAuth button.
        /// </summary>
        protected void btnTestOAuth_Click(object sender, EventArgs e)
        {
            var oauthSettings = new OAuth.OAuthConfig()
            {
                OAuthToken = txtOAuthAccessToken.Text,
                OAuthTokenSecret = txtOAuthAccessTokenSecret.Text,
                ConsumerKey = txtOAuthConsumerKey.Text,
                ConsumerSecret = txtOAuthConsumerSecret.Text,
                OAuthSignatureMethod = "HMAC-SHA1",
                OAuthVersion = "1.0"
            };

            // Display modal message of results
            try
            {
                var twitter = new Tweets.Library.Twitter(oauthSettings);
                var twitterUser = twitter.Authenticate();
                if (twitterUser != null)
                {
                    this.RenderSPDialogMessageBox("Success", "The specified OAuth credentials are valid for your authenticating Twitter account '" + twitterUser.ScreenName + "'.");
                }
                else
                {
                    this.RenderSPDialogMessageBox("Error", "It appears that your specified OAuth credentials are not valid. Please double check them and try again.");
                }
            }
            catch (Exception ex)
            {
                Logger.LogErrorLocal(EventId.Error, CategoryId.Error, ErrorLevel.High, "OAuth validation error.", ex);
                this.RenderSPDialogMessageBox("Error", "It appears that your specified OAuth credentials are not valid. Please double check them and try again. Error: " + ex.Message);
            }
        }

        /// <summary>
        /// Event handler of the OK button to schedule the job(s).
        /// </summary>
        protected void btnOK_Click(object sender, EventArgs e)
        {
            this.Validate("ArchiveTweets");
            if (this.IsValid == false)
            {
                return;
            }

            try
            {
                SPContext.Current.Web.AllowUnsafeUpdates = true;
                SPAdministrationWebApplication caWebApp = SPAdministrationWebApplication.Local;

                // Delete any existing jobs
                string jobName = Worker.JobTitle;
                foreach (SPJobDefinition job in caWebApp.JobDefinitions)
                {
                    // Delete any jobs having a matching name that starts with ours
                    if (job.Name.ToUpper().StartsWith(jobName.ToUpper()))
                    {
                        job.Delete();
                    }
                }

                // Persist settings
                AppData.Local.Entries[AppData.SHAREPOINT_SITE_URL] = txtSharePointSiteUrl.Text.Trim();
                AppData.Local.Entries[AppData.INCLUDE_ENTITIES] = ddlIncludeEntities.SelectedValue;
                AppData.Local.Entries[AppData.INCLUDE_RETWEETS] = ddlIncludeRetweets.SelectedValue;
                AppData.Local.Entries[AppData.OAUTH_ACCESS_TOKEN] = txtOAuthAccessToken.Text.Trim();
                AppData.Local.Entries[AppData.OAUTH_ACCESS_TOKEN_SECRET] = txtOAuthAccessTokenSecret.Text.Trim();
                AppData.Local.Entries[AppData.OAUTH_CONSUMER_KEY] = txtOAuthConsumerKey.Text.Trim();
                AppData.Local.Entries[AppData.OAUTH_CONSUMER_SECRET] = txtOAuthConsumerSecret.Text.Trim();
                AppData.Local.Entries[AppData.TWEET_COUNT] = txtTweetCount.Text.Trim();

                // Schedule new job
                switch (ddlSchedule.SelectedValue)
                {
                    case null:
                    case "":
                        {
                            // "None" was chosen - do nothing as above jobs were already deleted
                            // =====
                            // Reset back to "none" from the next time
                            AppData.Local.Entries[AppData.SELECTED_VALUE] = string.Empty;
                            AppData.Local.Update();

                            break;
                        }
                    case "Immediate":
                        {
                            // One time
                            SPSecurity.RunWithElevatedPrivileges(delegate()
                            {
                                var schedule = new SPOneTimeSchedule(DateTime.Now);
                                var job = new Worker(jobName, caWebApp)
                                {
                                    Schedule = schedule,
                                    Retry = true,
                                    Title = jobName
                                };
                                job.Update();
                            });

                            // Reset back to "none" for the next time
                            AppData.Local.Entries[AppData.SELECTED_VALUE] = string.Empty;
                            AppData.Local.Update();

                            break;
                        }
                    case "Daily":
                        {
                            // Store daily so that it is selected in the list when the page is opened again
                            AppData.Local.Entries[AppData.SELECTED_VALUE] = ddlSchedule.SelectedValue;
                            AppData.Local.Update();

                            // Multiple times daily
                            int i = 0;
                            foreach (RepeaterItem item in repPickerControls.Items)
                            {
                                if (item.ItemType == ListItemType.AlternatingItem || item.ItemType == ListItemType.Item)
                                {
                                    i++;

                                    // Update hour
                                    int hour = 0;
                                    var ddlHour = item.FindControl("ddlHour") as DropDownList;
                                    if (ddlHour != null)
                                    {
                                        hour = Convert.ToInt32(ddlHour.SelectedValue);

                                        AppData.Local.Entries[AppData.SCHEDULE_HOUR_TIME + i.ToString()] = hour.ToString();
                                        AppData.Local.Update();
                                    }

                                    // Updated enabled/disabled
                                    var chkScheduleEnabled = item.FindControl("chkScheduleEnabled") as CheckBox;
                                    if (chkScheduleEnabled != null)
                                    {
                                        AppData.Local.Entries[AppData.SCHEDULE_ENABLED + i.ToString()] = chkScheduleEnabled.Checked.ToString();
                                        AppData.Local.Update();

                                        // Schedule the job?
                                        if (chkScheduleEnabled.Checked == true)
                                        {
                                            SPSecurity.RunWithElevatedPrivileges(delegate()
                                            {
                                                // End hour and time
                                                int endhour = hour;
                                                var schedule = new SPDailySchedule()
                                                {
                                                    BeginHour = hour,
                                                    BeginMinute = 0,
                                                    EndHour = endhour,
                                                    EndMinute = 15 // End time where timer could start is up 15 mins later
                                                };

                                                string name = jobName + " " + i.ToString() + " (Daily at " + ddlHour.Text + ":00)";
                                                var job = new Worker(name, caWebApp)
                                                {
                                                    Schedule = schedule,
                                                    Retry = false,
                                                    Title = name
                                                };
                                                job.Update();
                                            });
                                        }
                                    } // if (chkScheduleEnabled != null)                                   

                                } // if (item.ItemType == ListItemType.Alternati...
                            }
                            break;
                        }
                }

                // Create the lists to hold the accounts and tweet items
                using (var site = new SPSite(txtSharePointSiteUrl.Text.Trim()))
                {
                    using (var web = site.RootWeb)
                    {
                        bool originalValue = web.AllowUnsafeUpdates;
                        web.AllowUnsafeUpdates = true;

                        // Create SP Archive Tweet lists
                        var list = new SPTweetList();
                        list.CreateListsIfNotPresent(web);

                        web.AllowUnsafeUpdates = originalValue;
                    };
                };

                SPUtility.Redirect(caWebApp.Sites[0].Url, SPRedirectFlags.Default, HttpContext.Current);
            }
            catch (Exception ex)
            {
                lblMessages.Text = ex.Message;
                Logger.LogErrorLocal(EventId.Error, CategoryId.Error, ErrorLevel.Unexpected, "An error occurred in setting the Twitter Archive schedule.", ex);
            }
        }

        /// <summary>
        /// Event handler of the Test URL button to delete the lists.
        /// </summary>
        private bool areListsCreated()
        {
            // Test the URL
            bool listsCreated = false;
            using (var site = new SPSite(txtSharePointSiteUrl.Text))
            {
                using (var web = site.RootWeb)
                {
                    try
                    {
                        bool catchException = SPSecurity.CatchAccessDeniedException;
                        SPSecurity.CatchAccessDeniedException = false;
                        try
                        {
                            var spList = new SPTweetList();
                            listsCreated = spList.DoesListExist(SPTweetList.AccountListName, web);
                        }
                        catch (Exception)
                        {
                            // Suppress exception, list does not exist
                        }
                        finally
                        {
                            // Reset the flag to original value
                            SPSecurity.CatchAccessDeniedException = catchException;
                        }
                    }
                    catch (Exception)
                    {
                        // Suppress exception, list does not exist
                    }
                };
            };

            return listsCreated;
        }

        /// <summary>
        /// Event handler of the Test URL button to delete the lists.
        /// </summary>
        protected void btnTestUrl_Click(object sender, EventArgs e)
        {
            // Test the URL
            using (var site = new SPSite(txtSharePointSiteUrl.Text))
            {
                using (var web = site.RootWeb)
                {
                    try
                    {
                        bool catchException = SPSecurity.CatchAccessDeniedException;
                        SPSecurity.CatchAccessDeniedException = false;
                        try
                        {
                            this.RenderSPDialogMessageBox("Success", "The specified URL is a valid SharePoint site.");
                        }
                        catch (Exception ex)
                        {
                            this.RenderSPDialogMessageBox("Error", "An error occurred. Ensure this is a valid SharePoint URL and that you have permission to create lists within it. " + ex.Message);
                        }
                        finally
                        {
                            // Reset the flag to original value
                            SPSecurity.CatchAccessDeniedException = catchException;
                        }
                    }
                    catch (Exception ex)
                    {
                        this.RenderSPDialogMessageBox("Error", "An error occurred. Ensure this is a valid SharePoint URL and that you have permission to create lists within it. " + ex.Message);
                    }
                };
            };
        }
    }
}
